﻿
using System;
using System.Collections.Generic;
using System.Linq;
using AutoMapper;
using Enferno.Public.Web.Models;
using Enferno.StormApiClient.ExposeProxy;
using Enferno.StormApiClient.Products;
using Enferno.Web.StormUtils;

using ParametricValue = Enferno.StormApiClient.Products.ParametricValue;

namespace Enferno.Public.Web.Builders
{
    public class ProductBuilder :
        ProductCustomVariantBuilder<ProductModel, VariantModel>
    {
        public ProductBuilder(ISiteRules rules) : base(rules)
        {

        }

    }

    public class ProductCustomProductBuilder<TProductModel> :
        ProductCustomVariantBuilder<TProductModel, VariantModel>
        where TProductModel :
            ProductWithCustomVariantModel<VariantModel>
    {
        public ProductCustomProductBuilder(ISiteRules rules)
            : base(rules)
        {

        }        
    }

    public class ProductCustomVariantBuilder<TProductModel, TVariantModel> :
        ProductBaseBuilder<
        TProductModel, 
        TVariantModel, 
        ProductManufacturerModel,
        ProductListCustomVariantModel<TVariantModel, ProductItemCustomVariantModel<TVariantModel>>,
        ProductItemCustomVariantModel<TVariantModel>, 
        FilterModel,
        ProductFileModel, 
        PriceModel, 
        OnHandStatusModel, 
        ParametricModel, 
        ParametricValueModel>
        where TProductModel :
            ProductWithCustomVariantModel<TVariantModel>
        where TVariantModel :
            VariantBaseModel<OnHandStatusModel, PriceModel, ProductFileModel, ParametricModel, ParametricValueModel>
    {
        public ProductCustomVariantBuilder(ISiteRules rules)
            : base(rules)
        {

        }
    }

    public class ProductBaseBuilder<
        TProductModel, 
        TVariantModel, 
        TProductManufacturerModel,
        TProductListModel,
        TProductItemModel,
        TFilterModel,
        TProductFileModel,
        TPriceModel,
        TOnHandStatusModel, 
        TParametricModel, 
        TParametricValueModel> : BuilderBase
        where TProductModel: ProductBaseModel<TVariantModel, TProductManufacturerModel, TProductListModel, TProductItemModel, TFilterModel, TProductFileModel, TPriceModel, TOnHandStatusModel, TParametricModel, TParametricValueModel>
        where TVariantModel: VariantBaseModel<TOnHandStatusModel, TPriceModel, TProductFileModel, TParametricModel, TParametricValueModel>
        where TProductManufacturerModel : ProductManufacturerModel
        where TProductListModel : ProductListBaseModel<TProductItemModel, TFilterModel, TVariantModel, TProductFileModel, TProductManufacturerModel, TPriceModel, TOnHandStatusModel, TParametricModel, TParametricValueModel>
        where TProductItemModel : ProductItemBaseModel<TVariantModel, TProductFileModel, TProductManufacturerModel, TPriceModel, TOnHandStatusModel, TParametricModel, TParametricValueModel>
        where TFilterModel : FilterModel
        where TProductFileModel : ProductFileModel, new()
        where TPriceModel : PriceModel
        where TOnHandStatusModel : OnHandStatusModel
        where TParametricModel: ParametricBaseModel<TParametricValueModel>, new()
        where TParametricValueModel: ParametricValueModel, new()
    {
        private TProductModel _model;

        public ProductBaseBuilder()
        {
            
        }

        public ProductBaseBuilder(ISiteRules rules)
            : base(rules)
        {
            
        }

        public TProductModel BuildProductModel(Product product)
        {
            return product == null ? null : MapProductModel(product);
        }


        protected TProductModel MapProductModel(Product product)
        {
            _model = Mapper.Map<Product, TProductModel>(product);
            AddProductImage(ProductFileType.DefaultImage, product.ImageKey, product.Name, _model.Files);
            MapVariants(product);
            MapParametrics(product, _model.Parametrics);
            MapFamilies(product);

            return _model;
        }

        protected void MapVariants(Product product)
        {
            if (product.Variants == null || product.Variants.Count == 0)
            {
                _model.Variants.Add(Mapper.Map<Product, TVariantModel>(product));               
                _model.SelectedVariant = _model.Variants[0];
            }
            else
            {
                _model.Price.IsFromPrice = product.Variants.Max(v => v.Price) != product.Variants.Min(v => v.Price);

                foreach (var variant in product.Variants)
                {
                    var variantModel = Mapper.Map<Product, TVariantModel>(variant);
                    AddProductImage(ProductFileType.VariantImage, variant.ImageKey, variant.Name, variantModel.Files);

                    MapVariantParametrics(variant, variantModel, _model);
                    MapParametrics(variant, variantModel.Parametrics);
                    _model.Variants.Add(variantModel);
                }
            }
        }

        protected static void MapVariantParametrics(Product variant, TVariantModel modelVariant, TProductModel model)
        {
            if (variant.VariantParametrics == null || variant.VariantParametrics.Count <= 0) return;
            foreach (var parametric in variant.VariantParametrics)
            {
                var parametricModel = MapParametric(parametric);
                if (parametricModel == null) continue;

                modelVariant.VariantParametrics.Add(parametricModel);

                var productModelParametric = model.VariantParametrics.Find(v => v.Id == parametricModel.Id);
                if (productModelParametric == null)
                {
                    productModelParametric = MapParametric(parametric);
                    model.VariantParametrics.Add(productModelParametric);
                }

                //add value to the product model level parametric if the parametric does not already contain that value
                foreach (var parametricValue in parametricModel.Values)
                {
                    //the value has an id, i.e it is of list type. Use that for uniqueness.
                    if (parametric.ValueId.HasValue &&
                        !productModelParametric.Values.Exists(value => value.Id == parametricValue.Id))
                    {
                        productModelParametric.Values.Add(parametricValue);
                    }
                    else if (!productModelParametric.Values.Exists(value => value.Value == parametricValue.Value)) //we don't have an id, use value for uniqueness
                    {
                        productModelParametric.Values.Add(parametricValue);
                    }    
                }

                productModelParametric.Values.Sort((p1, p2) => p1.SortOrder.CompareTo(p2.SortOrder));
            }
        }

        protected static void MapParametrics(Product product, List<TParametricModel> parametrics)
        {
            if (product.Parametrics == null || product.Parametrics.Count <= 0) return;
            parametrics.AddRange(product.Parametrics.Select(MapParametric).Where(p => p != null));
            parametrics.ForEach(p => p.Values.Sort((p1, p2) => p1.SortOrder.CompareTo(p2.SortOrder)));
        }

        protected void MapFamilies(Product product)
        {
            if (product.Families == null || product.Families.Count <= 0) return;
            foreach (var family in product.Families)
            {
                _model.Families.Add(new ProductFamilyModel
                {
                    Description = family.Description,
                    Id = family.Id,
                    Name = family.Name,
                    ImageUrl = GetImageUrl(family.ImageKey),
                });
            }
        }

        protected static TParametricModel MapParametric(ProductParametric p)
        {
            var parametricInfo = ApplicationDictionary.Instance.ParametricInfo(p.Id, StormContext.CultureCode);

            if (parametricInfo.Type == ParametricType.ListValue)
            {
                if (p.ValueId == null) return null;

                var parametric = MapProductParametric(p, parametricInfo);                
                var parametricValue = ApplicationDictionary.Instance.ParametricValue(parametricInfo.Type, p.ValueId.Value, StormContext.CultureCode);
                if (parametricValue == null) return parametric;
                var value = MapProductParametricValue(parametricValue);                
                parametric.Values.Add(value);
                return parametric;
            }
            
            if (parametricInfo.Type == ParametricType.MultiValue)
            {
                if (string.IsNullOrWhiteSpace(p.ValueIdSeed)) return null;
                var parametric = MapProductParametric(p, parametricInfo);
                foreach (var v in p.ValueIdSeed.Split(','))
                {
                    int id;
                    if(!int.TryParse(v, out id)) return null;
                    var parametricValue = ApplicationDictionary.Instance.ParametricValue(parametricInfo.Type, id, StormContext.CultureCode);
                    if (parametricValue == null) continue;
                    var value = MapProductParametricValue(parametricValue);
                    parametric.Values.Add(value);
                }
                return parametric;
            }

            

            if (p.ValueId.HasValue)
            {
                var value = new TParametricValueModel()
                {
                    Id = p.ValueId.Value,
                    Value = p.Value2 ?? p.Value
                };

                var parametric = MapProductParametric(p, parametricInfo);
                parametric.Values.Add(value);
                return parametric;
            }            
            else
            {
                var value = new TParametricValueModel()
                {
                    Value = p.Value2 ?? p.Value
                };
                var parametric = MapProductParametric(p, parametricInfo);
                parametric.Values.Add(value);
                return parametric;
            }
        }

        protected static TParametricValueModel MapProductParametricValue(ParametricValue parametricValue)
        {
            var value = (TParametricValueModel) Activator.CreateInstance(typeof (TParametricValueModel));
            value.Id = parametricValue.Id;
            value.Value = parametricValue.Name;
            value.Code = parametricValue.Code;
            value.Description = parametricValue.Description;
            value.ImageUrl = GetImageUrl(parametricValue.ImageKey);
            value.Name = parametricValue.Name;
            value.SortOrder = parametricValue.SortOrder;

            return value;
        }

        protected static TParametricModel MapProductParametric(ProductParametric p, ParametricInfo pi)
        {
            var parametric = new TParametricModel()
            {
                Id = p.Id,
                Name = p.Name,
                Description = p.Description,
                Uom = p.Uom,
                IsPrimary = p.IsPrimary,
                GroupId = p.GroupId,
                Group = p.GroupName,
                ValueType = pi.ValueType,
                Values = new List<TParametricValueModel>()
            };
            return parametric;
        }
    }
}
